#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#define VERSION 23
#define BUFSIZE 8096
#define ERROR      42
#define LOG        44
#define FORBIDDEN 403
#define NOTFOUND  404

#ifdef SLGCLD
#define SLGCLD SIGCHLD
#endif

struct {
  char *ext;
  char *filetype;
} extensions [] = {// 文件扩展名
  {"gif", "image/gif" },
  {"jpg", "image/jpg" },
  {"jpeg","image/jpeg"},
  {"png", "image/png" },
  {"ico", "image/ico" },
  {"zip", "image/zip" },
  {"gz",  "image/gz"  },
  {"tar", "image/tar" },
  {"htm", "text/html" },
  {"html","text/html" },
  {0,0} };

void logger(int type, char *s1, char *s2, int socket_fd)
{
  int fd ;
  char logbuffer[BUFSIZE*2];
  time_t t = time( 0 );
  int BUFLEN=255;
	char tmpBuf[BUFLEN];
 	strftime(tmpBuf, BUFLEN, "%Y-%m-%d %H:%M:%S", localtime(&t));
  switch (type) {
  case ERROR: (void)sprintf(logbuffer,"[%s] ERROR: %s:%s Errno=%d exiting pid=%d ErrorId:%d",tmpBuf, s1, s2, errno,getpid(),socket_fd);
    break;
  case FORBIDDEN:
    (void)write(socket_fd, "HTTP/1.1 403 Forbidden\nContent-Length: 185\nConnection: close\nContent-Type: text/html\n\n<html><head>\n<title>403 Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\nThe requested URL, file type or operation is not allowed on this simple static file webserver.\n</body></html>\n",271);
    (void)sprintf(logbuffer,"[%s] FORBIDDEN: %s:%s",tmpBuf, s1, s2);
    break;
  case NOTFOUND:
    (void)write(socket_fd, "HTTP/1.1 404 Not Found\nContent-Length: 136\nConnection: close\nContent-Type: text/html\n\n<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not Found</h1>\nThe requested URL was not found on this server.\n</body></html>\n",224);
    (void)sprintf(logbuffer,"[%s] NOT FOUND: %s:%s",tmpBuf, s1, s2);
    break;
  case LOG: (void)sprintf(logbuffer,"[%s] INFO: %s:%s:%d",tmpBuf, s1, s2,socket_fd); break;

  }
  /* No checks here, nothing can be done with a failure anyway */
  /*
    O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode ,表示该文件
                 的访问权限。
    O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
    O_TRUNC  如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断
                   (Truncate)为0字节。
    O_NONBLOCK 对于设备文件,以O_NONBLOCK 方式打开可以做非阻塞I/O(Nonblock I/O)
    O_APPEND   表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不
                     覆盖原来的内容。
    O_RDONLY 只读打开
    O_WRONLY 只写打开
    O_RDWR  可读可写打开
    用或是表示这几个选项全部生效 其中O_RDONLY O_WRONLY O_RDWR这三个必需写一个
  */
  if((fd = open("nweb.log", O_CREAT| O_WRONLY | O_APPEND,0644)) >= 0) {
    (void)write(fd,logbuffer,strlen(logbuffer));
    (void)write(fd,"\n",1);
    (void)close(fd);
  }
  if(type == ERROR || type == NOTFOUND || type == FORBIDDEN) exit(3);
}

/* this is a child web server process, so we can exit on errors */
void web(int fd,int hit){
    int j,file_fd,buflen;
    long i,ret,len;
    char* fstr;
    static char buffer[BUFSIZE+1];
    
    ret=read(fd,buffer,BUFSIZE);
    if(ret ==0|| ret==-1){
        logger(FORBIDDEN,"failed to read browser request","",fd);
    }
    
    if(ret>0 && ret<BUFSIZE){ //return code is vaild chars
        buffer[ret]=0;
    }
    else{
        buffer[0]=0;
    }

    for(i=0;i<ret;i++){
        if(buffer[i]=='\r'||buffer[i]=='\n'){
            buffer[i]='*';
        }
    }
    logger(LOG,"request",buffer,hit); //TODO : what the hit mean? 
    if( strncmp(buffer,"GET ",4) && strncmp(buffer,"get ",4)){
        logger(FORBIDDEN,"Only simple GET operation supported",buffer,fd);
    }

    for(i=4;i<BUFSIZE;i++){ //null terminate after the second space to ignore extra stuff
        if(buffer[i]==' '){
            buffer[i]=0;
            break;
        }
    }
    
    for(j=0;j<i-1;j++){ // check for illegal parent directory use
        if(buffer[j]=='.'&&buffer[j+1]=='.'){
          logger(FORBIDDEN,"Parent directory (...) path names not supported",buffer,fd);
        }
    }
    if(!strncmp(&buffer[0],"GET /\0",6)||strncmp(&buffer[0],"get /\0",6)){ // \0 means the end of string
      // convert no filename to index file
      (void)strcpy(buffer,"GET /index.html");
    }
    // work out the file type and check we support it
    buflen=strlen(buffer);
    fstr=(char*)0;
    for(i=0;extensions[i].ext!=0;i++){
        len=strlen(extensions[i].ext);//后缀文件
        if(!strncmp(&buffer[buflen-len],extensions[i].ext,len)){
          fstr=extensions[i].filetype;
          break;
        }
    }
    if(fstr==0){
      logger(FORBIDDEN,"file extension type not supported",buffer,fd);
    }
    if((file_fd=open(&buffer[5],O_RDONLY))==-1){
      logger(FORBIDDEN,"failed to open file",&buffer[5],fd);
    }
    logger(LOG,"SEND",&buffer[5],hit);
    len=(long)lseek(file_fd,(off_t) 0,SEEK_END);// lseek to the file end to find the length
    (void)lseek(file_fd,(off_t)0,SEEK_SET); // lseek back to the file start ready for reading
    (void)sprintf(buffer,"HTTP/1.1 200 OK\nServer: nweb/%d.0\nContent-Length:%ld\nConnection:close\nContent-Type: %s\n\n",VERSION,len,fstr); // head + a blank line
    logger(LOG,"Header",buffer,hit);
    (void)write(fd,buffer,strlen(buffer));

    //send file in 8KB block  last block may be smaller
    while ((ret=read(file_fd,buffer,BUFSIZE))>=0){
      (void)write(fd,buffer,ret);
    }
    //防止socket关闭时未传输完毕信息
    sleep(1); //allow socket to drain before signalling the socket is closed
    close(fd);
    
}                             

int main(int argc,char **argv){
  int i,port,listenfd,socketfd,hit;
  socklen_t length;
  static struct sockaddr_in cli_addr;
  static struct sockaddr_in serv_addr;

  if(argc<3 || argc>3 || !strcmp(argv[1],"-?")){
    (void)printf("hint: nweb Port-Number Top-Directory\t\tversion %d\n\n"
    "\tnweb is a small and very safe mini web server\n"
    "\tnweb only servers out file/web pages with extensions named below\n"
    "\t and only from the named directory or its sub-directories.\n"
    "\tThere is no fancy features = safe and secure.\n\n"
    "\tExample:webserver 8181 /home/nwebdir &\n\n"
    "\tOnlt Supports:",VERSION);
    for(i=0;extensions[i].ext!=0;i++){
      (void)printf(" %s",extensions[i].ext);
    }
    (void)printf("\n\tNot Supported: URLs including \"..\", Java, Javascript, CGI\n"
  "\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n"
  "\tNo warranty given or implied\n\tNigel Griffiths nag@uk.ibm.com\n"  );
    exit(0);
  }
  if( !strncmp(argv[2],"/"   ,2 ) || !strncmp(argv[2],"/etc", 5 ) ||
      !strncmp(argv[2],"/bin",5 ) || !strncmp(argv[2],"/lib", 5 ) ||
      !strncmp(argv[2],"/tmp",5 ) || !strncmp(argv[2],"/usr", 5 ) ||
      !strncmp(argv[2],"/dev",5 ) || !strncmp(argv[2],"/sbin",6) ){
    (void)printf("ERROR: Bad top directory %s, see nweb -?\n",argv[2]);
    exit(3);
  }
  if(chdir(argv[2]) == -1){
    printf("argv2 :%s\n",argv[2]);
    (void)printf("ERROR: Can't Change to directory %s\n",argv[2]);
    exit(4);
  }
  if(fork()!=0){
    return 0;
  }
  (void)signal(SIGCLD,SIG_IGN);
  (void)signal(SIGHUP,SIG_IGN);
  for(i=0;i<32;i++){
    (void)close(i);
  }
  (void)setpgrp();
  logger(LOG,"nweb starting",argv[1],getpid());

  //set up networksocket
  if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0){
    logger(ERROR,"system call","socket",0);
  }
  port=atoi(argv[1]);
  if(port<0 || port >60000){
    logger(ERROR,"Invaild port number (try 1->60000)",argv[1],0);
  }
  serv_addr.sin_family=AF_INET;
  serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  serv_addr.sin_port=htons(port);
  int errorValue=0;
  if ( (errorValue=bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr))) <0){
    logger(ERROR,"system call","bind",errorValue);
  }
  //64 通讯接口
  if(listen(listenfd,64)<0){
    logger(ERROR,"system call","listen",0);
  }
  //always wait ,webserver never end
  for(hit=1;;hit++){
    length=sizeof(cli_addr);
    if((socketfd=accept(listenfd,(struct sockaddr *)&cli_addr,&length))<0){
      logger(ERROR,"system call","accept",0);
    }
    web(socketfd,hit);
    printf("in loop %d",hit);
  }
  
  //ssh开机自启动
  //https://dulunar.github.io/2019/10/14/windows10%E4%B8%8BWSL%E7%9A%84%E4%BD%BF%E7%94%A8%E5%92%8C%E9%85%8D%E7%BD%AE/
}